#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <string>
#include <Display/Renders/PointRender/point_render_cpu.h>
#include <Utilities/gl_utilities.h>
using namespace std;

#define STRINGIFY(A) #A
static const std::string PointRenderVertexShader = string("#version 450 core\n")+STRINGIFY(
layout(location = 0) in vec3 position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
	gl_Position = projection * view * model * vec4(position, 1.0f);
}
);

static const std::string PointRenderFragmentShader = string("#version 450 core\n")+ STRINGIFY(
out vec4 color;
void main()
{
	color = vec4(1.0f, 1.0f, 0.0f, 1.0f);
}
);

namespace PhysLeo {

template<typename T>
PointRenderCpu<T>::PointRenderCpu():num_points_(0), vao_(0), vbo_(0), writable_(false),cnt_(0),save_frames_(0)
{
}

template<typename T>
PointRenderCpu<T>::~PointRenderCpu()
{
	glDeleteVertexArrays(1,&vao_);
	glDeleteBuffers(1, &vbo_);
}

template<typename T>
void PointRenderCpu<T>::init(SimData<T> &simData)
{
	shader_.createFromString(PointRenderVertexShader, PointRenderFragmentShader);
    ptr_pos_ = simData.ptr_pos_;
    num_points_ = ptr_pos_->size();
    ptr_pos_copy_ = std::make_shared<BufferData<glm::vec3>>(num_points_);
    ptr_pos_copy_->allocateCpu();

	//allocate gl render resource
	if (vao_ != 0)glDeleteVertexArrays(1, &vao_);
	glGenVertexArrays(1, &vao_);
	if (vbo_ != 0)glDeleteBuffers(1, &vbo_);
	glGenBuffers(1, &vbo_);
	glBindVertexArray(vao_);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * num_points_, nullptr, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), static_cast<GLvoid*>(nullptr));
	glEnableVertexAttribArray(0);
	glBindVertexArray(0);
}

template<typename T>
void PointRenderCpu<T>::draw(CameraInterface &camera)
{
	//memoryCopy();

	shader_.use();
	//model view projection
	//calculate projection matrix
	auto proj_mat = glm::perspective(glm::radians(camera.getFov()), camera.getAspect(), camera.getNearClip(), camera.getFarClip());

	//calculate model view matrix
	const auto glm_camera_pos = camera.getPosition();
	const auto glm_camera_focus_pos = camera.getFocusPosition();
	const auto glm_camera_up_dir = camera.getUpDirection();

	auto model_view_mat = glm::lookAt(glm_camera_pos, glm_camera_focus_pos, glm_camera_up_dir);
	auto identity = glm::mat4(1.0);
	// Get the uniform locations
	const auto modelLoc = glGetUniformLocation(shader_.id(), "model");
	const auto viewLoc = glGetUniformLocation(shader_.id(), "view");
	const auto projLoc = glGetUniformLocation(shader_.id(), "projection");
	// Pass the matrices to the shader
	glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(model_view_mat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj_mat));
	glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(identity));

	glVerify(glBindVertexArray(vao_));
	glPointSize(5.0);
	glVerify(glDrawArrays(GL_POINTS, 0, num_points_));
	glVerify(glBindVertexArray(0));
	shader_.unUse();
}

template<typename T>
void PointRenderCpu<T>::transformToDrawData()
{
    const auto pos_ptr = ptr_pos_->ptrCpu();
    const auto pos_copy_ptr = ptr_pos_copy_->ptrCpu();
    for(auto i=0;i<num_points_;++i)
    {
        pos_copy_ptr[i][0] = static_cast<float>(pos_ptr[i][0]);
        pos_copy_ptr[i][1] = static_cast<float>(pos_ptr[i][1]);
        pos_copy_ptr[i][2] = static_cast<float>(pos_ptr[i][2]);
    }
    exportPointXyz();
}

template<typename T>
void PointRenderCpu<T>::updateDrawData()
{
    glBindBuffer(GL_ARRAY_BUFFER, vbo_);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3 * num_points_, ptr_pos_copy_->ptrCpu());
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

template<typename T>
void PointRenderCpu<T>::exportPointXyz()
{
    if (writable_)
    {
        char namebuff[200];
        sprintf(namebuff, "%s_%05d.txt", filename_.c_str(), cnt_);
        fstream fileout;
        fileout.open(namebuff, std::ios::out | std::ios::binary);
        cout << namebuff << endl;
        assert(fileout.is_open());

        ptr_pos_copy_->gpu2Cpu();

        const auto temp_ptr = ptr_pos_copy_->ptrCpu();
        auto num_vert = ptr_pos_copy_->size();

        //write vertex
        fileout.write((char*)&num_vert, sizeof(int));
        for (int i = 0; i < num_vert; ++i)
        {
            fileout.write((char*)&(temp_ptr[i][0]), sizeof(float));
            fileout.write((char*)&(temp_ptr[i][1]), sizeof(float));
            fileout.write((char*)&(temp_ptr[i][2]), sizeof(float));
        }



        fileout.close();
        cout << "save file " << namebuff << endl;

        cnt_++;
        if (cnt_ > save_frames_)
        {
            writable_ = false;
        }
    }
}

template class PointRenderCpu<float>;
template class PointRenderCpu<double>;
}